Skip to content

Add Bazel build with BoringSSL support#501

Open
BYVoid wants to merge 9 commits into
ClickHouse:masterfrom
BYVoid:bazel-and-boringssl
Open

Add Bazel build with BoringSSL support#501
BYVoid wants to merge 9 commits into
ClickHouse:masterfrom
BYVoid:bazel-and-boringssl

Conversation

@BYVoid
Copy link
Copy Markdown

@BYVoid BYVoid commented May 27, 2026

Summary

  • Adds a Bazel (bzlmod) build alongside the existing CMake build, so downstream Bazel projects can depend on clickhouse-cpp via the Bazel Central Registry without standing up CMake.
  • TLS is selectable via a build flag: --@clickhouse_cpp//:tls=boringssl|openssl|no (default: boringssl). The two TLS libraries come from BCR modules and are linked statically, keeping the build hermetic; tls=no disables TLS entirely, matching CMake's WITH_OPENSSL=OFF default.
  • The two OpenSSL-only surfaces — SSL_CONF_* command API and SSL_read_ex — are gated behind a USE_BORINGSSL define in clickhouse/base/sslsocket.cpp, set only when building with the BoringSSL backend. When USE_BORINGSSL is undefined (CMake, or Bazel with tls=openssl) the original code paths are used unchanged.
  • A GitHub Actions workflow builds the Bazel target on Linux and macOS across all three TLS modes.
  • The CMake build is untouched.

What's added

File Purpose
MODULE.bazel Module declaration; deps: abseil-cpp, bazel_skylib, boringssl, cityhash, lz4, openssl, rules_cc, zstd (all latest on BCR)
MODULE.bazel.lock Pinned dep graph
BUILD.bazel :tls string flag + config settings; the public :clickhouse cc_library target
.github/workflows/bazel.yml CI: bazel build //... on Linux + macOS × `tls=boringssl
.gitignore Ignore bazel-* symlinks
clickhouse/base/sslsocket.cpp Ifdef-guarded BoringSSL compat patch

TLS backend selection

bazel build //:clickhouse                                  # BoringSSL (default)
bazel build //:clickhouse --@clickhouse_cpp//:tls=openssl  # OpenSSL from BCR
bazel build //:clickhouse --@clickhouse_cpp//:tls=no       # TLS disabled

Implemented with a bazel_skylib string_flag + config_setting + select() on srcs, defines, and deps. BoringSSL is the default because it builds reliably across platforms on BCR today and matches what many Bazel workspaces (gRPC, Envoy) already link. tls=no omits WITH_OPENSSL, excludes sslsocket.cpp from the build, and links no TLS library — the same shape as CMake's IF (WITH_OPENSSL). The same mechanism is the natural place for future build options (e.g. CH_MAP_BOOL_TO_UINT8).

sslsocket.cpp patch detail

Two #ifdef USE_BORINGSSL ... #else ... #endif regions:

  1. configureSSL() body — BoringSSL has no SSL_CONF_* API, so the commands cannot be applied. If SSLParams::configuration is non-empty, a one-time warning is printed to stderr pointing at the OpenSSL build option, instead of silently dropping potentially security-relevant settings.
  2. SSLSocketInput::DoRead() — under USE_BORINGSSL falls back to SSL_read with len clamped to INT_MAX. SSL_read_ex is an OpenSSL 3.0+ API not in BoringSSL.

Function signatures, declarations, SSLParams::ConfigurationType, validateParams, etc. all stay intact, so the public API is unchanged regardless of which TLS backend is linked.

Test plan

  • bazel build //:clickhouse (BoringSSL default) produces libclickhouse.a on darwin-arm64 (Bazel 9.1.1) and Linux.
  • bazel build //:clickhouse --@clickhouse_cpp//:tls=openssl builds OpenSSL 3.5.5 from source and links successfully.
  • bazel build //:clickhouse --@clickhouse_cpp//:tls=no builds without any TLS dependency.
  • GitHub Actions workflow covering the Bazel build on Linux + macOS for all three tls modes.

Notes for reviewers

  • All Bazel dependencies come from BCR, including cityhash@1.0.2 (1.0.2 is the algorithm revision ClickHouse's wire checksums require). The contrib/ trees are untouched and still used by the CMake build.
  • Once merged, I plan to submit the module to the Bazel Central Registry so consumers can depend on it with a single bazel_dep.

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented May 27, 2026

CLA assistant check
All committers have signed the CLA.

@BYVoid BYVoid force-pushed the bazel-and-boringssl branch from 5ff5053 to a2c545a Compare May 27, 2026 04:26
@BYVoid BYVoid marked this pull request as ready for review May 27, 2026 04:29
Copilot AI review requested due to automatic review settings May 27, 2026 04:29
@BYVoid BYVoid requested review from mzitnik and slabko as code owners May 27, 2026 04:29
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds Bazel (bzlmod) build support for clickhouse-cpp and adapts TLS codepaths to work with BoringSSL.

Changes:

  • Added MODULE.bazel with bzlmod deps (abseil, boringssl, lz4, zstd, rules_cc).
  • Added BUILD.bazel with :clickhouse cc_library and vendored :cityhash.
  • Updated TLS implementation to compile under BoringSSL by disabling SSL_CONF_* usage and using SSL_read instead of SSL_read_ex.

Reviewed changes

Copilot reviewed 3 out of 5 changed files in this pull request and generated 3 comments.

File Description
clickhouse/base/sslsocket.cpp Adds BoringSSL-specific branches for SSL configuration and reading APIs.
MODULE.bazel Introduces a bzlmod module definition and external deps for Bazel builds.
BUILD.bazel Adds Bazel targets to build/export the library and its vendored cityhash dependency.
.gitignore Ignores Bazel output symlinks/directories.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread BUILD.bazel
Comment thread clickhouse/base/sslsocket.cpp
Comment thread clickhouse/base/sslsocket.cpp
@BYVoid
Copy link
Copy Markdown
Author

BYVoid commented May 27, 2026

Why BoringSSL (and not OpenSSL) in this PR

A few people will probably ask why TLS goes through @boringssl rather than @openssl here. Some context:

  1. OpenSSL on BCR still has rough edges. The current OpenSSL modules don't build cleanly out of the box across all platforms/toolchains we tried, while @boringssl builds reliably with no extra rules.
  2. Single-TLS-in-process constraint. Many Bazel workspaces already link BoringSSL transitively (gRPC, Envoy, abseil-adjacent libs). Pulling in OpenSSL alongside leads to duplicate symbols, conflicting libssl/libcrypto initialization, and ODR violations. Picking BoringSSL keeps clickhouse-cpp compatible with that ecosystem without forcing consumers to fight their dep graph.

The USE_BORINGSSL define is intentionally a compile-time switch, not hardcoded behavior — the OpenSSL-only code paths are still present in sslsocket.cpp and active whenever USE_BORINGSSL is undefined (which is the case for the existing CMake build).

Follow-up: once OpenSSL's BCR story stabilizes, it's straightforward to add a Bazel config_setting (e.g. --@clickhouse_cpp//:tls=openssl|boringssl) that flips the defines and the deps between @openssl and @boringssl, letting consumers choose. Wanted to keep this PR scoped to a single working configuration first.

@slabko
Copy link
Copy Markdown
Contributor

slabko commented Jun 1, 2026

@BYVoid, thank you for your contribution!
I think this is a very good idea, indeed.

However, before we proceed, I’d like to clarify two things:

  1. Are you willing to help maintain the Bazel integration after merge? In particular, fixing Bazel dependency breakages, Bazel version issues, and compatibility reports from the users. I understand this cannot be a formal guarantee, but I want to avoid merging a build system that immediately becomes unowned.

  2. Could you add a GitHub Actions workflow that build and test the project. Linux-only is fine as a starting point.

@BYVoid
Copy link
Copy Markdown
Author

BYVoid commented Jun 1, 2026

Yes. I can help maintain the Bazel build support and probably submit it to https://registry.bazel.build/ in near future.

I will try to add a github worklow to cover the Bazel build.

Adds a Linux Bazel CI job that runs bazel build //... so the
bzlmod build is exercised on push and pull requests, mirroring the
existing CMake workflows.
@BYVoid
Copy link
Copy Markdown
Author

BYVoid commented Jun 1, 2026

@slabko I added a Github workflow. Can you grant me the permission to run it?

@slabko
Copy link
Copy Markdown
Contributor

slabko commented Jun 4, 2026

Hi @BYVoid,

I am trying to learn the basics of Bazel before we merge it. This is still in progress, but my plan is to merge it soon.

I have a couple of new questions:

  1. You included BoringSSL as a replacement for OpenSSL. I understand why that would be necessary. However, is there also an option to link to the system-bundled version of OpenSSL? This is usually more desirable on Linux, since the Linux distribution would take care of patching and updating it when needed. Similarly, on macOS, dynamically linking against a version of OpenSSL installed by Homebrew might be more desirable. In fact, this is how the library works now: it dynamically links to whatever version of OpenSSL is available on the system. How do people approach that with Bazel? Do they ever link against system-installed libraries?
  2. The same question applies to zstd, lz4, and Abseil. Abseil is only used for wide integers, such as 128-bit and 256-bit integers, and my plan is to remove any dependencies on Abseil and remove its parts components from the project. Instead, the library will provide access to raw data, so users can use whichever libraries they prefer. However, I think that with the way it is implemented right now, we can simply remove the dependency from the dependency list once the library is ready. Just a heads-up.
  3. The library has some options (like WITH_SYSTEM_CITYHASH, or CH_MAP_BOOL_TO_UINT8) that change how it is built and linked. I can see that you have chosen a very strict approach, which makes sense for now. However, how will we be able to introduce configuration and compatibility flags in the future?

@BYVoid
Copy link
Copy Markdown
Author

BYVoid commented Jun 4, 2026

Great questions! They all trace back to Bazel's core idea: hermetic builds. A Bazel build should depend only on declared, version-pinned inputs, never on whatever happens to be installed on the host. Everything is built from pinned sources and (typically) statically linked, so the same commit produces the same binary on any machine. This is also the prevailing convention on the Bazel Central Registry: modules build their dependencies from source and avoid system packages whenever possible. The ecosystem takes this idea quite far, even with efforts like hermetic-llvm that make the compiler toolchain itself a hermetic part of the build.

1. System OpenSSL: Linking system libraries is possible in Bazel but goes against this philosophy, so I'd rather not make it the default. Your concern about security patching is valid, but the Bazel ecosystem solves it differently: instead of relying on the distro to swap a shared library at runtime, the usual workflow is to bump the pinned version in MODULE.bazel and rebuild + redeploy. Tools like Renovate/Dependabot automate these version bumps, and BoringSSL itself is maintained by Google with timely BCR updates, so the patch latency is comparable in practice. Also, consumers who really want a system lib can already override @boringssl in their own MODULE.bazel, with no support needed from this library.

2. zstd/lz4/Abseil: same treatment, all pinned BCR modules. Thanks for the heads-up on Abseil! Once you remove it, the Bazel side is just deleting one line from MODULE.bazel and one from deps.

3. Build options: Bazel has user-defined flags for exactly this. See ccronexpr's BUILD.bazel for an example. Options like CH_MAP_BOOL_TO_UINT8 or a future tls=boringssl|openssl switch map naturally onto bool_flag + select(). I kept this PR single-configuration on purpose, and flags can be added incrementally.

Overall my suggestion: let the Bazel build stay hermetic. Since every dependency version is fully under control, many enterprise users actually prefer this model: the resulting binaries carry no expectations about what's installed on the host, which to some extent simplifies container (Docker) images, often down to a minimal base image plus the binary. For users who prefer system-installed dependencies, CMake remains the right tool. The two builds serve different philosophies, and that's fine. 🙂

BYVoid added 2 commits June 4, 2026 10:10
Adds --@clickhouse_cpp//:tls=boringssl|openssl (default: boringssl).
Both TLS implementations come from BCR modules and are linked
statically, keeping the build hermetic. USE_BORINGSSL and the TLS
deps are now switched via select() on the flag.
BoringSSL doesn't ship the SSL_CONF_* command API, so
SSLParams::configuration cannot be applied. Print a one-time warning
to stderr instead of silently dropping potentially security-relevant
settings, and point users at the OpenSSL build option.
@BYVoid
Copy link
Copy Markdown
Author

BYVoid commented Jun 4, 2026

@slabko I added a tls build flag.

BYVoid added 3 commits June 4, 2026 10:25
abseil-cpp 20260107.1 -> 20260526.0
boringssl 0.20260508.0 -> 0.20260526.0
rules_cc 0.2.18 -> 0.2.19
cityhash 1.0.2 is now available on the Bazel Central Registry, so the
Bazel build no longer compiles the vendored copy under
contrib/cityhash/. The contrib/ tree is still used by the CMake build.
Build matrix: {ubuntu-24.04, macos-latest} x {boringssl, openssl}.
@slabko
Copy link
Copy Markdown
Contributor

slabko commented Jun 5, 2026

I changed the CI rules so they no longer run without approval, but GitHub still asks me to approve them every time for PRs that were created before that change. I’m sorry for the inconvenience, but after we merge this one, I don’t think this should happen again.

@BYVoid
Copy link
Copy Markdown
Author

BYVoid commented Jun 5, 2026

Thanks. What do you think of the current build flag setup? I can add tls=no mode to disable SSL in addition to openssl and boringssl if you think it's necessary.

@slabko
Copy link
Copy Markdown
Contributor

slabko commented Jun 5, 2026

@BYVoid,

If you can add the tls=no option, that would be great. Then it would be consistent with the current behavior with cmake.

@BYVoid
Copy link
Copy Markdown
Author

BYVoid commented Jun 5, 2026

Done — added tls=no as a third value of the flag. It omits WITH_OPENSSL, excludes sslsocket.cpp from the build, and links no TLS library, mirroring CMake's WITH_OPENSSL=OFF default. The CI matrix now covers all three modes (boringssl/openssl/no) on both Linux and macOS, and I updated the PR description accordingly.

@slabko
Copy link
Copy Markdown
Contributor

slabko commented Jun 5, 2026

@BYVoid,

I’m still in the phase of asking noob-level questions, so I hope you can bear with me. I’ve just tried importing clickhouse_cpp into a test project using Bazel. Here’s my test MODULE.bazel:

module(name = "playgraoud", version = "0.1.1")

bazel_dep(name = "rules_cc", version = "0.2.17")

bazel_dep(name = "clickhouse_cpp", version = "2.6.1")
git_override(
    module_name = "clickhouse_cpp",
    remote = "https://github.com/BYVoid/clickhouse-cpp.git",
    commit = "d6f0fafb9ed958b7422c7eff9ebfc5d55735e58a",
)

With Bazel 9.0.1 I get this error:

ERROR: Error computing the main repository mapping: bazel_tools@_ depends on abseil-cpp@20250814.1 with compatibility level 1, but clickhouse_cpp@_ depends on abseil-cpp@20260526.0 with compatibility level 0 which is different

It does not happen on 9.1.1, but it does happen on older versions of Bazel. I understand that bazel_tools itself also depends on Abseil, and if different versions are pinned, we end up in this unfortunate situation. I was wondering what the right way to deal with this is.

As a workaround, I forced clickhouse_cpp to use another version of Abseil:

single_version_override(
    module_name = "abseil-cpp",
    version = "20250814.1",
)

However, this feels a bit strange. Does Bazel force the client to use a specific version of Abseil based on its own version?

@BYVoid
Copy link
Copy Markdown
Author

BYVoid commented Jun 5, 2026

Good find — I reproduced it with Bazel 9.0.1 and tracked down what's going on. It's an unfortunate interaction between three things:

  1. bzlmod's compatibility_level check. Unlike a plain version conflict (where MVS just picks the highest version), two dependents requiring different compatibility levels of the same module is a hard error — Bazel has no way to know the two levels are interchangeable.

  2. bazel_tools has its own abseil dependency. Bazel ≤ 9.0.x's built-in bazel_tools module depends on abseil-cpp@20250814.1, which declares compatibility_level = 1.

  3. abseil dropped the attribute in its newest release. Starting with Bazel 9.1.0, compatibility_level is a no-op and module maintainers are advised to stop declaring it (bazelbuild/bazel#28616). abseil-cpp@20260526.0 — the version this PR pinned — followed that guidance and removed compatibility_level = 1, which makes it default to 0.

So on Bazel 9.1+ the check no longer exists and everything resolves fine, but on ≤ 9.0.x the still-active check sees level 1 (from bazel_tools) vs level 0 (from us) and refuses. To answer your question directly: yes, on older Bazel versions bazel_tools' own abseil pin effectively constrains which abseil compatibility level the rest of the dep graph can use. Your single_version_override workaround is the textbook consumer-side fix, but the library shouldn't make you need it.

Fix pushed: I pinned abseil-cpp to 20260107.1 — the last version that still declares compatibility_level = 1 — so the module imports cleanly on both old and new Bazel. Verified locally with Bazel 9.0.1 using your exact test setup (no override needed).

Why this won't be an issue going forward: on Bazel ≥ 9.1 the compatibility-level check is gone entirely, so any mix of abseil versions resolves by plain MVS; and once bazel_tools' world catches up (or its abseil dep disappears), newer level-less abseil releases are unproblematic even where the check survives, because both sides of the graph will be level-0. And as you mentioned abseil is slated for removal from the library anyway — at that point this whole pin disappears along with the dependency.

@BYVoid
Copy link
Copy Markdown
Author

BYVoid commented Jun 5, 2026

One more note on the abseil pin, for users who want a newer abseil than the 20260107.1 this module now pins:

On Bazel 9.1+, the cleanest way is a plain bazel_dep in the consumer's own MODULE.bazel — no single_version_override needed:

bazel_dep(name = "abseil-cpp", version = "20260526.0")  # or any newer release

bzlmod selects a single global version per module via MVS (the highest version requested anywhere in the dep graph), so this upgrades abseil for the entire build — including the copy clickhouse_cpp itself is compiled against. There is never a second abseil in the graph. I verified this end-to-end: with Bazel 9.1.1 and the bazel_dep above, bazel mod graph shows the whole graph resolving to 20260526.0 and @clickhouse_cpp//:clickhouse builds against it successfully.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants